package org.bgee.psp4jdbc.jdbcapi;

import static org.junit.Assert.*;

import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bgee.psp4jdbc.Properties;
import org.bgee.psp4jdbc.TestAncestor;
import org.bgee.psp4jdbc.pool.Pool.EvictionMethod;
import org.h2.jdbcx.JdbcDataSource;
import org.junit.Test;
import org.junit.BeforeClass;
import org.junit.AfterClass;

public class RealLifeIT extends TestAncestor {
	/**
     * Logger of this class
     */
    private final static Logger log = 
            LogManager.getLogger(RealLifeIT.class.getName());

    @Override
    protected Logger getLogger()
    {
        return log;
    } 
    
    private static javax.sql.DataSource realDataSource;
    private static final String REALURL = "jdbc:h2:mem:db1";
    private static final String REALDRIVERNAME = "org.h2.Driver";
    
    @BeforeClass
    public static void initialize() throws ClassNotFoundException, SQLException {
    	Class.forName(REALDRIVERNAME);
    	Class.forName("org.bgee.psp4jdbc.jdbcapi.Driver");
    	
    	JdbcDataSource realSource = new JdbcDataSource();
    	realSource.setURL(REALURL);
    	realDataSource = realSource;
    	
    	java.sql.Connection realCon = realSource.getConnection();
    	java.sql.Statement stmt = realCon.createStatement();
    	stmt.executeUpdate("create table test (key varchar(10), value int)");
    	stmt.executeUpdate("insert into test values ('a', 1), ('b', 2)");
    }
    @AfterClass
    public static void release() {
    	realDataSource = null;
    }
    
    /**
     * Play a bit with the library.
     */
    @Test
    public void shouldPlayABit() throws SQLException {
    	DataSource source = new DataSource(realDataSource);
    	source.setPspEvictionFactor(0.1f);
    	source.setPspEvictionMethod(EvictionMethod.LRU);
    	source.setPspGlobalMaxSize(4);
    	source.setPspPoolMaxSize(2);
    	
    	Connection con1 = source.getConnection();
    	assertFalse("Real connection incorrectly initialized", 
    			con1.getRealConnection().isClosed());
    	
    	
    	
    	String url = Properties.URLPREFIX + REALURL + ";IFEXISTS=TRUE?" + 
                Properties.DRIVERCLASSNAMEKEY + "=" + REALDRIVERNAME + "&" + 
        		Properties.EVICTIONMETHODKEY + "=LFU&" + 
                Properties.EVICTIONFACTORKEY + "=1&" + 
        		Properties.GLOBALMAXSIZEKEY + "=4&" + 
                Properties.POOLMAXSIZEKEY + "=2";
        	
    	Connection con2 = (Connection) DriverManager.getConnection(url);
    	assertFalse("Real connection incorrectly initialized", 
    			con2.getRealConnection().isClosed());
    	
    	
    	

    	PreparedStatement statement1 = con1.prepareStatement("select value from test where key = ?");
    	assertFalse("Real PreparedStatement incorrectly initialized", 
    			statement1.getRealStatement().isClosed());
    	statement1.setString(1, "a");
    	ResultSet result = statement1.executeQuery();
    	result.first();
    	assertEquals("Incorrect value returned", 1, result.getInt(1));

    	

    	PreparedStatement statement2 = con2.prepareStatement("select value from test where key = ?");
    	assertFalse("Real PreparedStatement incorrectly initialized", 
    			statement2.getRealStatement().isClosed());
    	assertNotSame("2 different Connections returned same PreparedStatements", 
    			statement1, statement2);
    	statement2.setString(1, "b");
    	ResultSet result2 = statement2.executeQuery();
    	result2.first();
    	assertEquals("Incorrect value returned", 2, result2.getInt(1));
    	statement2.close();
    	
    	
    	
    	
    	statement1.close();
    	assertFalse("Underlying real PreparedStatement was actually closed", 
    			statement1.getRealStatement().isClosed());
    	//get the same statement again
    	assertSame("A same Connection returned different PreparedStatemen for a same query", 
    			statement1, con1.prepareStatement("select value from test where key = ?"));
    	//if we try the same query, as the PreparedStatement is currently not in the pool, 
    	//we should get a new one
    	PreparedStatement statement3 = 
    			con1.prepareStatement("select value from test where key = ?");
    	assertNotSame("A Connection returned a PreparedStatement not in pool", 
    			statement1, statement3);
    	//Return statement1 to the pool
    	statement1.close();
    	assertFalse("Underlying real PreparedStatement was actually closed", 
    			statement1.getRealStatement().isClosed());
    	//now if we want to close statement3, as the same query is already in the pool, 
    	//it should be close for real
    	statement3.close();
    	assertTrue("A PreparedStatement was not released as expected", 
    			statement3.getRealStatement().isClosed());
    	
    	
    	//now let's add to the pool two new PreparedStatements to trigger the eviction
    	PreparedStatement statement4 = 
    			con1.prepareStatement("select * from test where key = ?");
    	statement4.close();
    	PreparedStatement statement5 = 
    			con1.prepareStatement("select value from test where key = ? and value = ?");
    	statement5.close();
    	//statement1 should have been released, and only this one
    	assertTrue("A PreparedStatement was not released as expected", 
    			statement1.getRealStatement().isClosed());
    	assertFalse("Underlying real PreparedStatement was actually closed", 
    			statement4.getRealStatement().isClosed());
    	assertFalse("Underlying real PreparedStatement was actually closed", 
    			statement5.getRealStatement().isClosed());
    	
    	
    	//now we try to trigger the global eviction mechanism 
    	//let's add one PreparedStatement to the second Connection
    	PreparedStatement statement6 = 
    			con2.prepareStatement("select * from test where key = ?");
    	statement6.close();
    	//we should now have 2 PreparedStatements in con1 pool, 2 in con2 pool
    	//we change the global max size allowed to 2
    	Properties.setGlobalMaxSize(2);
    	//trying to add a PreparedStatement anywhere should trigger eviction 
    	//on both connections (they have the same size)
    	PreparedStatement statement7 = 
    			con2.prepareStatement("select * from test where key = ? and value = ?");
    	statement7.close();
    	//statement4 should have been closed from con1 (least recently used eviction)
    	//and because the eviction factor of con2 is 1 (release all), 
    	//statement2 and statement6 should have been closed
    	assertTrue("A PreparedStatement was not released as expected", 
    			statement4.getRealStatement().isClosed());
    	assertTrue("A PreparedStatement was not released as expected", 
    			statement2.getRealStatement().isClosed());
    	assertTrue("A PreparedStatement was not released as expected", 
    			statement6.getRealStatement().isClosed());
    	
    	//statement5 and statement7 should still be available
    	assertFalse("Underlying real PreparedStatement was actually closed", 
    			statement5.getRealStatement().isClosed());
    	assertSame("A same Connection returned different PreparedStatemen for a same query", 
    			statement5, con1.prepareStatement("select value from test where key = ? and value = ?"));
    	assertFalse("Underlying real PreparedStatement was actually closed", 
    			statement7.getRealStatement().isClosed());
    	assertSame("A same Connection returned different PreparedStatemen for a same query", 
    			statement7, con2.prepareStatement("select * from test where key = ? and value = ?"));
    	
    	//use them for real, just to be sure
    	statement5.setString(1, "a");
    	statement5.setInt(2, 1);
    	result = statement5.executeQuery();
    	result.first();
    	assertEquals("Incorrect value returned", 1, result.getInt(1));
    	statement5.close();
    	
    	statement7.setString(1, "b");
    	statement7.setInt(2, 2);
    	result = statement7.executeQuery();
    	result.first();
    	assertEquals("Incorrect value returned", 2, result.getInt(2));
    	statement7.close();
    	
    	//release the pools now
    	con1.close();
    	con2.close();
    	assertTrue("Real connection was not closed as expected", 
    			con1.getRealConnection().isClosed());
    	assertTrue("Real connection was not closed as expected", 
    			con2.getRealConnection().isClosed());
    	assertTrue("A PreparedStatement was not released as expected", 
    			statement5.getRealStatement().isClosed());
    	assertTrue("A PreparedStatement was not released as expected", 
    			statement7.getRealStatement().isClosed());
    }
}
